/**
* @file idt.cpp
* Interrupt Description table interface for X86 platform.
* Contains data structures and function for IDT managment:
* to define new interrupt, generate interrupt, set location of IDT to processor,
* interrupt callback etc...
*
* Copyrights 2010 Michal Saman, m.saman@designplus.cz.
* This source code is release under the Apache License 2.0.
* http://www.apache.org/licenses/LICENSE-2.0
*/

#include <string.h>
#include <main/kprintf.h>
#include <hal/idt.h>

#pragma pack(push, 1)
/**
* Data structure descripting size and location IDT in memory.
*/
struct Idtr {
	uint16_t	limit;			/**< size of IDT in memory in bytes */
	uint32_t	base;			/**< base address of IDT in memory */
};
#pragma pack(pop)

/** Array of descriptors = Interrupt Descritor Table */
static struct IdtDescriptor	idt[IDT_MAX_INTERRUPTS];
/** idtr hold Idtr structure to help define cpu's IDTR register */
static struct Idtr			idtr;

static void idt_install();
static void idt_default_handler();

/**
* Set CPU's IDTR special register to point to our IDT.
*/
static void idt_install() {
	__asm__ volatile ("lidt %0" : : "m"(idtr) );
};

/**
* This routine serve ALL unhandled system interrupts.
* Writes on console, that unhandled IRQ will be called.
*/
static __attribute__ ((cdecl)) void idt_default_handler() {
	__asm__ volatile ("cli");
	__asm__ volatile ("pushal");

	//kprintf("\n[HAL] idt_default_handler: Warning - unhandled Interrupt Request!\n\n");

	__asm__ volatile ("popal");
	//__asm__ volatile ("movl %ebp, %esp");
	//__asm__ volatile ("popl %ebp");
	__asm__ volatile ("sti");
	__asm__ volatile ("iretl");
};

/**
* Function returns IDT descriptor on specified position index.
* @param i	Position index to specified which descriptor we wants.
* @returns IDT descriptor address.
*/
struct IdtDescriptor * idt_get_descriptor(uint32_t i) {
	if (i>IDT_MAX_INTERRUPTS)
		return 0;
	return &idt[i];
};

/**
* Set descriptor to IDT on specifie location.
* @param 	i				Number of interrupt / Sets ordere location in IDT (number).
* @param 	flags			Descriptor bit flags.
* @param 	code_selector	Code selector where is interrupt handler function located.
* @param ER	irq				Routine to handle specified interrupt.
* @returns Zero if success.
*/
int idt_set_descriptor(uint32_t i, uint16_t flags, uint16_t code_selector, IRQ_HANDLER irq) {
	if (i>IDT_MAX_INTERRUPTS) 
		return 0;
	if (!irq)
		return 0;
	// get base address of interrupt handler
	uint32_t uiBase = (uint32_t) &(*irq);
	// WORKAROUND for GCC compiler, skip first 3 bytes of code, it's standart function stub code for save ESP to EBP (we dont need it);
	uiBase += 6;

	idt[i].baseLo = (uint16_t)(uiBase & 0xFFFF);
	idt[i].baseHi = (uint16_t)((uiBase>>16)&0xFFFF);
	idt[i].reserved = 0;
	idt[i].flags = (uint8_t)flags;
	idt[i].code_selector = code_selector;

	return 0;
};

/** 
* Inicialize default system Interrupt Description Table.
* @param code_selector	GDT code selector in which default IRQ_HANDLER is located.
* @returns Zero if succes.
*/
int idt_init(uint16_t code_selector) {
	idtr.limit = sizeof(struct IdtDescriptor) * IDT_MAX_INTERRUPTS - 1;
	idtr.base = (uint32_t) &idt[0];
	kprintf("IDT loaded at: 0x%x\n", idt);
	// zero-fill whole IDT
	memset((void*)&idt[0], 0, sizeof(struct IdtDescriptor) * IDT_MAX_INTERRUPTS - 1);

	int i;
	for (i=0; i<IDT_MAX_INTERRUPTS; i++)
		idt_set_descriptor(i, IDT_DESC_PRESENT|IDT_DESC_BIT32, code_selector, (IRQ_HANDLER) idt_default_handler);

	idt_install();
	return 0;
};
